﻿#region Usings

using System;
using System.Collections.Generic;
using System.Windows.Media;
using Box2DX.Collision;
using Box2DX.Common;
using Box2DX.Dynamics;

#endregion

namespace SilverBox.Physics
{
    public class Simulator
    {
        private float accumulator = 0;
        private SimpleContactListener contactListener;
        private SimulatorContext context;
        private List<Entity> entities;
        private float interval;
        private DateTime lastTime = DateTime.MinValue;
        private PhysicsContext physicsContext;
        private float totalElapsedTime = 0;
        private World world;

        public Simulator(PhysicsContext physicsContext, Vector2 screenTopLeft, Vector2 screenBottomRight,
                         Vector2 worldTopLeft, Vector2 worldBottomRight, float gravity, float interval)
        {
            this.physicsContext = physicsContext;
            context = new SimulatorContext(screenTopLeft, screenBottomRight, worldTopLeft, worldBottomRight);
            var worldAABB = new AABB();
            worldAABB.LowerBound = new Vector2(-200, -200);
            worldAABB.UpperBound = new Vector2(200, 200);
            world = new World(worldAABB, new Vector2(0, gravity), true);
            contactListener = new SimpleContactListener();
            world.SetContactListener(contactListener);
            this.interval = interval;
            World.SetBoundaryListener(new SimpleBoundaryListener(HandleBoundaryViolation));
        }

        public float Interval
        {
            get { return interval; }
        }

        public Vector2 WorldSize { get; set; }

        public SimulatorContext SimulatorContext
        {
            get { return context; }
        }

        public World World
        {
            get { return world; }
        }

        public bool IsRunning { get; private set; }

        public List<Entity> Entities
        {
            get
            {
                if (entities == null)
                {
                    entities = new List<Entity>();
                }
                return entities;
            }
        }

        public PhysicsContext PhysicsContext
        {
            get { return physicsContext; }
        }

        public void AddEntity(Entity entity, BodyParameters bodyParameters)
        {
            entity.Initialize(physicsContext, bodyParameters);
            Entities.Add(entity);
        }

        public void RemoveEntity(Entity entity)
        {
            if (entity.Body != null)
                World.DestroyBody(entity.Body);

            Entities.Remove(entity);
        }

        public void Start()
        {
            IsRunning = true;
            CompositionTarget.Rendering += OnTimerElapsed;
            lastTime = DateTime.Now;
        }

        private void OnTimerElapsed(object sender, EventArgs e)
        {
            Update();
        }


        public void Update()
        {
            DateTime currentTime = DateTime.Now;
            TimeSpan elapsedTime = currentTime - lastTime;
            lastTime = currentTime;
            var elapsed = (float) elapsedTime.TotalMilliseconds;
            accumulator += elapsed;

            while (accumulator > interval)
            {
                World.Step(interval*0.001f, 10);
                totalElapsedTime += interval;
                accumulator -= interval;
            }

            contactListener.ProcessContacts();

            foreach (var entity in Entities)
            {
                if (entity.Body != null)
                {
                    Vector2 blockPosition = entity.BodyPosition;
                    double rotation = entity.BodyAngle;

                    if (blockPosition.IsValid && !entity.Body.IsStatic)
                    {
                        // TODO fix this; block position is centered, element position isn't
                        Vector2 position = blockPosition - (entity.BodySize*0.5f);
                        entity.ElementPosition = entity.Simulator.SimulatorContext.WorldToScreen(position);
                        entity.ElementAngle = rotation;
                    }
                }
            }
        }

        public void Stop()
        {
            IsRunning = false;
            CompositionTarget.Rendering -= OnTimerElapsed;
        }

        private void HandleBoundaryViolation(Body body)
        {
            var entity = body.UserData as Entity;
            if (entity != null)
                entity.OnBoundaryViolation();
        }
    }
}